package session.stateful;

import entity.BranchEntity;
import entity.CapacityControlEntity;
import entity.TouristAccountEntity;
import entity.FnBBranchEntity;
import entity.FnBProductEntity;
import entity.FnBReservationEntity;
import entity.MealEntity;
import entity.ReservationEntity;
import entity.ReservationPaymentItemEntity;
import entity.ReservedMealEntity;
import entity.ReservedSetMealEntity;
import entity.ReservedTableEntity;
import entity.SetMealEntity;
import entity.StakeholderEntity;
import entity.TableTypeEntity;
import entity.TransactionsEntity;
import java.io.IOException;
import java.sql.Time;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import javax.ejb.EJB;
import javax.ejb.Stateless;
import javax.ejb.LocalBean;
import javax.mail.MessagingException;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import util.entityhelper.ReservationHelper;

@Stateless
@LocalBean
public class ReservationSessionBean {

    private static double CANCELFEE = 5;
    @PersistenceContext
    EntityManager entityManager;
    @EJB
    EmailSessionBean emailSessionBean;
    @EJB
    PayPalSessionBean paypalSessionBean;
    

    /*
     * Purpose: Add new reservation
     * Steps:
     * 1) Link between branch and reservation
     * 2) Create the reserved meals
     * 3) Create the reserved set meals
     * 4) Create the reserved tables
     * 5) Link between tourist and reservation
     * 6) Link between transaction and reservation
     * (No need to link between transaction and customer as they
     * are linked in the TransactionSessionBean)
     * 7) Set the confirmation status
     * 8) Return the confirmation status to tourist
     */
    public String addReservation(FnBReservationEntity reservation, Long touristId, long branchId,
            List<List<FnBProductEntity>> choiceList, Map<Long, Double> priceList, Map<Long, Integer> quantityList,
            List<TableTypeEntity> tableList, int[] selectedQuantity, long transactionId) throws MessagingException {
        TouristAccountEntity tourist = entityManager.find(TouristAccountEntity.class, touristId);
        FnBBranchEntity branch = entityManager.find(FnBBranchEntity.class, branchId);
        entityManager.persist(reservation);
        branch.getReservation().add(reservation);
        reservation.setBranch(branch);


        List<MealEntity> listOfMeal = new ArrayList<MealEntity>();
        for (int i = 0; i < choiceList.size() - 1; i++) {
            for (FnBProductEntity p : choiceList.get(i)) {
                listOfMeal.add((MealEntity) p);
            }
        }
        changeReservedMeal(reservation, listOfMeal, reservation.getReservedMeal(), priceList, quantityList);

        List<SetMealEntity> listOfSet = new ArrayList<SetMealEntity>();
        if (!choiceList.isEmpty()) {
            for (FnBProductEntity p : choiceList.get(choiceList.size() - 1)) {
                listOfSet.add((SetMealEntity) p);
            }
            changeReservedSet(reservation, listOfSet, reservation.getReservedSetMeal(), priceList, quantityList);
        }

        changeReservedTable(reservation, reservation.getReservedTable(), tableList, selectedQuantity);

        tourist.getReservation().add(reservation);
        entityManager.merge(tourist);
        reservation.setTourist(tourist);

        TransactionsEntity transaction = entityManager.find(TransactionsEntity.class, transactionId);
        transaction.setReservation(reservation);
        reservation.getTransactions().add(transaction);

        entityManager.flush();
        if (!branch.getFnBIOTR().isAutoConfirm() || checkOverload(branch, reservation)) {
            reservation.setConfirmation("Not confirmed");
            String senderAddress = "is3102team32@gmail.com";
            String recipientAddress = branch.getStakeholder().getCorpEmail();
            String subject = "Confirm for reservation id " + reservation.getId();
            String message = "Customer " + tourist.getTourist().getLastName()
                    + " has made a reservation with branch " + branch.getBranchName() + " on date "
                    + getDateFormat(reservation.getDateReserved()) + " from "
                    + getTimeFormat(reservation.getTimeBegin()) + getTimeFormat(reservation.getTimeEnd())
                    + ".\n" + "Please log in to the system to manually confirm this reservation.\n"
                    + "Best Regards.\n" + "TCube teams.";
            emailSessionBean.setPassword("emsongtronguocao");
            emailSessionBean.setUser("is3102team32");
            emailSessionBean.sendMail(senderAddress, recipientAddress, subject, message);


            subject = "Info about Reservation ID " + reservation.getId();
            recipientAddress = tourist.getTourist().getEmail();
            message = "Thank you for making reservation with branch " + branch.getBranchName() + " for date " + getFormatReservedDate(reservation.getDateReserved())
                    +" at " + getTimeFormat(reservation.getTimeBegin()) +
                    ". Please wait for the restaurant to confirm your booking. We hope you will understand and sympathize.\nBest Regards.\n TCube teams.";
            emailSessionBean.sendMail(senderAddress, recipientAddress, subject, message);
            return "Please wait for the restaurant to confirm the reservation";
        }

        String subject = "Confirmation for Reservation ID " + reservation.getId();
        String recipientAddress = tourist.getTourist().getEmail();
        String message = "Thank you for making reservation with branch " + branch.getBranchName() + " for date " + getFormatReservedDate(reservation.getDateReserved())
                +" at " + getTimeFormat(reservation.getTimeBegin())
                + ". We are pleased to confirm your booking with us. We hope you will enjoy your time at the restaurant.\nBest Regards.\n TCube teams.";
        String senderAddress = "is3102team32@gmail.com";
        emailSessionBean.setPassword("emsongtronguocao");
        emailSessionBean.setUser("is3102team32");
        emailSessionBean.sendMail(senderAddress, recipientAddress, subject, message);

        reservation.setConfirmation("Confirmed");
        reservation.setStatus("Pending");
        return "Your reservation has been confirmed";
    }

    /*
     * Purpose: Check whether the number of table booked exceed X%, with X
     * is the predefined percentage of booking set by branch. Range of X
     * is from [0,100]
     * Steps:
     * 1) Get the percentage from the branch
     * 2) For each table booked by this visitor, check whether this reservation
     * makes the number of tables booked exceed X%. If yes, return true. Otherwise
     * at the end return false.
     */
    private boolean checkOverload(FnBBranchEntity branch, FnBReservationEntity reservation) {
        List<ReservedTableEntity> list = reservation.getReservedTable();
        double percent = branch.getFnBIOTR().getPercentManualConfirm();
        for (ReservedTableEntity temp : list) {
            TableTypeEntity table = temp.getTableType();
            int[][] used = getUsed(table, reservation.getDateReserved());
            int maximum = getMaximum(table, reservation.getDateReserved());
            for (int i = 0; i < 24; i++) {
                for (int j = 0; j < 4; j++) {
                    if ((used[i][j] * 1.0 / maximum) * 100 > percent) {
                        return true;
                    }
                }
            }
        }
        return false;
    }

    public List<TableTypeEntity> getTableList(long branchId) {
        FnBBranchEntity branch = entityManager.find(FnBBranchEntity.class, branchId);
        return branch.getTableType();
    }

    public int[][] getAvailTable(long tableId, Date dateReserved) {
        TableTypeEntity table = entityManager.find(TableTypeEntity.class, tableId);

        int maximum = getMaximum(table, dateReserved);
        int[][] used = getUsed(table, dateReserved);

        for (int i = 0; i < used.length; i++) {
            for (int j = 0; j < used[i].length; j++) {
                used[i][j] = maximum - used[i][j];
            }
        }

        return used;
    }
    /*
     * Purpose: get the maximum table for this type of table on this day
     * Steps:
     * 1) Get the capacity control list of this table type
     * 2) Go through the list and check for these types, with the priority
     * decreasing:
     * a) Type 8: Specific Date, if found this type 8, compare whether the
     * specific date = date booked. If yes, return the quantity
     * b) Type 1 -> 7: Stands for Sunday, Monday..., Saturday
     * c) Type 0: default number
     */

    private int getMaximum(TableTypeEntity table, Date dateReserved) {
        List<CapacityControlEntity> capacityList = table.getCapacityControl();

        int normal = -1;
        int dayOfWeek = - 1;
        int specific = -1;
        int date = dateReserved.getDay() + 1;

        for (CapacityControlEntity c : capacityList) {
            if (c.getType() == 8) {
                Date tempDate = c.getSpecificDate();
                if (compareDate(tempDate, dateReserved)) {
                    specific = c.getNumberOfTable();
                }
            } else if (date == c.getType()) {
                dayOfWeek = c.getNumberOfTable();
            } else {
                normal = c.getNumberOfTable();
            }
        }

        if (specific != - 1) {
            return specific;
        }

        if (dayOfWeek != - 1) {
            return dayOfWeek;
        }

        return normal;
    }

    /*
     * Purpose: Get the number of this type of table booked for this day
     * Steps:
     * 1) Get the full list of table reserved of this table type
     * 2) For each table reserved, check whether the date reserved equals
     * to the date booked
     * 3) If yes, increase each time slot occupied by number of tables booked
     * for this reservation
     * 4) Return back the result array
     */
    private int[][] getUsed(TableTypeEntity table, Date dateReserved) {
        List<ReservedTableEntity> fullList = table.getReservedTable();

        int[][] time = new int[24][4];

        for (ReservedTableEntity t : fullList) {
            FnBReservationEntity r = t.getFandBReservation();
            if ((compareDate(r.getDateReserved(), dateReserved))) {
                int beginHour = r.getTimeBegin().getHours();
                int beginMinute = r.getTimeBegin().getMinutes();

                int endHour = r.getTimeEnd().getHours();
                int endMinute = r.getTimeEnd().getMinutes();

                while (beginHour != endHour || beginMinute != endMinute) {
                    time[beginHour][beginMinute / 15] += t.getQuantity();

                    beginHour += (beginMinute + 15) / 60;
                    beginMinute = (beginMinute + 15) % 60;
                    beginHour = beginHour % 24;
                }
            }
        }
        return time;
    }

    private boolean compareDate(Date d1, Date d2) {
        return d1.getDate() == d2.getDate() && d1.getMonth() == d2.getMonth() && d1.getYear() == d2.getYear();
    }

    public int getIntervalByBranchId(long branchId) {
        FnBBranchEntity branch = entityManager.find(FnBBranchEntity.class, branchId);

        double eatingTime = branch.getFnBIOTR().getEatingTime();
        return getInterval(eatingTime);
    }

    /*
     * Purpose: to convert the estimated eating time of each branch from
     * double format to number of 15-minutes intervals.
     * Steps:
     * 1) Get the eating time under double format
     * 2) Split into hour and minute (type int)
     * 3) Return hour * 4 + minute;
     */
    private int getInterval(double eatingTime) {
        int hour = (int) eatingTime;
        int minute = 0;
        double minuteEat = (eatingTime - hour) * 100;

        if (minuteEat <= 0) {
            minute = 0;
        } else if (minuteEat <= 25) {
            minute = 1;
        } else if (minuteEat <= 50) {
            minute = 2;
        } else if (minuteEat <= 75) {
            minute = 3;
        }

        return hour * 4 + minute;
    }

    public List<ReservationEntity> getReservationListByBranchId(long id) {
        FnBBranchEntity branch = entityManager.find(FnBBranchEntity.class, id);
        return branch.getReservation();
    }

    public void toShow(Long id) {
        // Modified by Cong Dat - just copy the whole method
        FnBReservationEntity fandBReservation = entityManager.find(FnBReservationEntity.class, id);
        fandBReservation.setStatus("Show");
        TouristAccountEntity tourist = fandBReservation.getTourist();
        double point = tourist.getPoint();
        double addedPoint = 0;
        List<ReservedMealEntity> mealList = fandBReservation.getReservedMeal();
        for (ReservedMealEntity meal : mealList) {
            point += meal.getQuantity() * meal.getPointReceived();
            addedPoint = addedPoint + meal.getQuantity() * meal.getPointReceived();
        }

        List<ReservedSetMealEntity> setList = fandBReservation.getReservedSetMeal();
        for (ReservedSetMealEntity set : setList) {
            point += set.getQuantity() * set.getPointReceived();
            addedPoint = addedPoint + set.getQuantity() * set.getPointReceived();
        }

        tourist.setPoint(point);
        entityManager.merge(fandBReservation);
        entityManager.merge(tourist);
        entityManager.flush();

        // Added by Cong Dat
        // Functionality C.1.2 - Notify rewarding points to customers via email. 
        // No points will be awarded for table reservations
        if (addedPoint == 0) {
            SimpleDateFormat sdf = new SimpleDateFormat("dd-MM-yyyy");
            String reservationDetail = "Reservation Number : " + fandBReservation.getId() + ".\n"
                    + "Reservation Date : " + sdf.format(fandBReservation.getDateReserved()) + ".\n"
                    + "Reservation Time : from " + fandBReservation.getTimeBegin().getHours() + ":" + fandBReservation.getTimeBegin().getMinutes() + " to "
                    + fandBReservation.getTimeEnd().getHours() + ":" + fandBReservation.getTimeEnd().getMinutes() + "\n"
                    + "Reservation Status : Showed / Presented\n";
            String emailMessage = "Dear " + tourist.getTourist().getFirstName() + " " + tourist.getTourist().getLastName()
                    + ", \n\nThank you for purchasing on FoodSage.sg.\n\n"
                    + "This is to acknowledge that you have presented at restaurant " + fandBReservation.getBranch().getBranchName() + " for your reservation number " + fandBReservation.getId()
                    + "Here is the detail of your reservation status: \n"
                    + reservationDetail + "\n"
                    + "\nTotal number of points you get for this reservation : " + addedPoint + " points.\n"
                    + "Currently you have accumulated : " + tourist.getPoint() + " in your Loyalty Point Account.\n"
                    + "You can use these points to redeem free gifts and items offered on our website.\n"
                    + "\nAlso, if you have free time, do not forget to visit our website to enjoy many other great deals and promotions offered on FoodSage.sg. \n"
                    + "Have a nice day. \n\n FoodSage.sg. ";
            try {
                emailSessionBean.setUser("is3102team32@gmail.com");
                emailSessionBean.setPassword("emsongtronguocao");
                emailSessionBean.sendMail("is3102team32@gmail.com", tourist.getTourist().getEmail(), "FoodSage.sg - Confirmation on Point Redemption for "
                        + tourist.getTourist().getFirstName() + " " + tourist.getTourist().getLastName(), emailMessage);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    public void toNoShow(Long id) throws MessagingException {
        FnBReservationEntity fandBReservation = entityManager.find(FnBReservationEntity.class, id);
        fandBReservation.setStatus("No Show");
        TouristAccountEntity tourist = fandBReservation.getTourist();
        double amtStakeholder = fandBReservation.getDeposit();
        double amtTCube = 0;
        List<ReservedMealEntity> mealList = fandBReservation.getReservedMeal();
        for (ReservedMealEntity meal : mealList) {
            amtTCube += meal.getQuantity() * meal.getNoShowCharge();
        }

        List<ReservedSetMealEntity> setList = fandBReservation.getReservedSetMeal();
        for (ReservedSetMealEntity set : setList) {
            amtTCube += set.getQuantity() * set.getNoShowCharge();
        }
        entityManager.merge(fandBReservation);
        entityManager.flush();
        createReservationPaymentItem(fandBReservation, amtStakeholder, amtTCube, " reported as no show");

        String subject = "Reported as no show on date " +   getFormatReservedDate(fandBReservation.getDateReserved())
                +" at " + getTimeFormat(fandBReservation.getTimeBegin()) + " for reservation id " + id;
        String recipientAddress = tourist.getTourist().getEmail();
        String message = "Branch " + fandBReservation.getBranch().getBranchName() + " has reported you don't show up on date " + getFormatReservedDate(fandBReservation.getDateReserved())
                +" at " + getTimeFormat(fandBReservation.getTimeBegin())
                + ". Therefore, you don't receive any points for your loyalty program account. In case you think there are mistakes, feel free to contact us"
                + "at this email support@tcube.com.\nBest Regards.\n TCube teams.";
        String senderAddress = "is3102team32@gmail.com";
        emailSessionBean.setPassword("emsongtronguocao");
        emailSessionBean.setUser("is3102team32");
        emailSessionBean.sendMail(senderAddress, recipientAddress, subject, message);
    }

    private void createReservationPaymentItem(FnBReservationEntity res, double amtStakeholder, double amtTCube, String status) {
        ReservationPaymentItemEntity payment = new ReservationPaymentItemEntity();
        StakeholderEntity stakeholder = res.getBranch().getStakeholder();
        Date date = res.getDateReserved();
        String explanation = "Payment for reservation id " + res.getId()
                + " on date " + getDateFormat(date) + status + ".";
        payment.setExplanation(explanation);
        payment.setDateCreated(new Date());
        payment.setStakeholder(stakeholder);
        payment.setAmountToStakeholder(amtStakeholder);
        payment.setAmountToTCube(amtTCube);
        entityManager.persist(payment);

        stakeholder.getPaymentItem().add(payment);
        entityManager.merge(payment);
    }

    public void toConfirmed(Long id) throws MessagingException {
        FnBReservationEntity fandBReservation = entityManager.find(FnBReservationEntity.class, id);
        fandBReservation.setConfirmation("Confirmed");
        entityManager.merge(fandBReservation);
        entityManager.flush();
                
        //inform the tourist
        TouristAccountEntity tourist = fandBReservation.getTourist();
        String subject = "Confirmation for Reservation ID " + id;
        String recipientAddress = tourist.getTourist().getEmail();
        String message = "Thank you for making reservation with branch " + fandBReservation.getBranch().getBranchName() + " for date " + getFormatReservedDate(fandBReservation.getDateReserved())
                +" at " + getTimeFormat(fandBReservation.getTimeBegin())
                + ". We are pleased to confirm your booking with us. We hope you will enjoy your time at the restaurant.\nBest Regards.\n TCube teams.";
        String senderAddress = "is3102team32@gmail.com";
        emailSessionBean.setPassword("emsongtronguocao");
        emailSessionBean.setUser("is3102team32");
        emailSessionBean.sendMail(senderAddress, recipientAddress, subject, message);       
    }

    public void toRejected(Long id) throws MessagingException, IOException {
        FnBReservationEntity fandBReservation = entityManager.find(FnBReservationEntity.class, id);
        fandBReservation.setConfirmation("Rejected");

        //remove all the meal, set meals and table associated with this booking
        List<ReservedMealEntity> listMeal = fandBReservation.getReservedMeal();
        while (!listMeal.isEmpty()) {
            ReservedMealEntity res = listMeal.get(0);
            res.getMeal().getReservedMeal().remove(res);
            entityManager.merge(res.getMeal());
            listMeal.remove(0);
            entityManager.remove(res);
        }

        List<ReservedSetMealEntity> listSet = fandBReservation.getReservedSetMeal();
        while (!listSet.isEmpty()) {
            ReservedSetMealEntity res = listSet.get(0);
            res.getSetMeal().getReservedSetMeal().remove(res);
            entityManager.merge(res.getSetMeal());
            listSet.remove(0);
            entityManager.remove(res);
        }

        List<ReservedTableEntity> listTable = fandBReservation.getReservedTable();
        while (!listTable.isEmpty()) {
            ReservedTableEntity res = listTable.get(0);
            res.getTableType().getReservedTable().remove(res);
            entityManager.merge(res.getTableType());
            listSet.remove(0);
            entityManager.remove(res);
        }

        entityManager.merge(fandBReservation);
        entityManager.flush();
        
        //inform the tourist
        TouristAccountEntity tourist = fandBReservation.getTourist();
        String subject = "Rejections for Reservation ID " + id;
        String recipientAddress = tourist.getTourist().getEmail();
        String message = "Thank you for making reservation with branch " + fandBReservation.getBranch().getBranchName() + " for date " + getFormatReservedDate(fandBReservation.getDateReserved())
                +" at " + getTimeFormat(fandBReservation.getTimeBegin())
                + ". Unfortunately, due to the overbooking on this date, we are sorry to inform you that we have to reject your booking. We will"
                + "refund all the deposits you made to us excluding the change fees (if applicable)"
                + ". We seek your understanding. Besides, you can try to book at another time slot for the same branch or at another branch."
                + "\nBest Regards.\n TCube teams.";
        String senderAddress = "is3102team32@gmail.com";
        emailSessionBean.setPassword("emsongtronguocao");
        emailSessionBean.setUser("is3102team32");
        emailSessionBean.sendMail(senderAddress, recipientAddress, subject, message);

        //refund back to them
        List<TransactionsEntity> list = fandBReservation.getTransactions();
        int count = 0;
        int num = list.size();
        double changeFee = getChangeFeeByReservationId(fandBReservation.getId());
        for (; count < num; count++) {
            TransactionsEntity trans = list.get(count);
            double amount = trans.getAmount();
            if (count != 0) {
                amount -= changeFee;
            }

            paypalSessionBean.refundPayPal(trans, amount, "Refund for cancelling reservation "
                    + id, "Refund");
        }
    }

    //Thai Duy
    public ReservationEntity getReservationById(Long id) {
        return entityManager.find(ReservationEntity.class, id);
    }
//Thai Duy 

    public List<FnBReservationEntity> getFnBReservationListByTouristId(Long id) {
        TouristAccountEntity tourist = entityManager.find(TouristAccountEntity.class, id);

        List<FnBReservationEntity> result = new ArrayList<FnBReservationEntity>();
        List<ReservationEntity> list = tourist.getReservation();

        for (ReservationEntity r : list) {
            if (r instanceof FnBReservationEntity) {
                result.add((FnBReservationEntity) r);
            }
        }

        return result;
    }

    public List<ReservedTableEntity> getReservedTableByReservationId(Long id) {
        FnBReservationEntity reservation = entityManager.find(FnBReservationEntity.class, id);

        return reservation.getReservedTable();
    }

    public List<ReservedMealEntity> getReservedMealByReservationId(Long id) {
        FnBReservationEntity reservation = entityManager.find(FnBReservationEntity.class, id);

        return reservation.getReservedMeal();
    }

    public List<ReservedSetMealEntity> getReservedSetMealByReservationId(Long id) {
        FnBReservationEntity reservation = entityManager.find(FnBReservationEntity.class, id);

        return reservation.getReservedSetMeal();
    }

    /*
     * Purpose: Change a reservation made by a tourist. The condition is that
     * the tourist is only allowed to change the table, meals and set meals
     * booked, number of people accompanied or special request but not the 
     * time nor the date
     * Steps:
     * 1) Change the table booked
     * 2) Change the meal booked
     * 3) Change the set meal booked
     * 4) Merge the reservation so that other changes like payment made,
     * deposit paid, number of people accompanied and special request will
     * be persisted in the database
     */
    public void changeReservation(FnBReservationEntity reservation, List<List<FnBProductEntity>> choiceList, Map<Long, Double> priceList, Map<Long, Integer> quantityList, List<TableTypeEntity> tableList, int[] selectedQuantity) {
        changeReservedTable(reservation, reservation.getReservedTable(), tableList, selectedQuantity);

        List<MealEntity> listOfMeal = new ArrayList<MealEntity>();
        List<SetMealEntity> listOfSet = new ArrayList<SetMealEntity>();

        List<ReservedMealEntity> listOfReservedMeal = reservation.getReservedMeal();
        List<ReservedSetMealEntity> listOfReservedSet = reservation.getReservedSetMeal();

        for (int i = 0; i < choiceList.size() - 1; i++) {
            for (FnBProductEntity product : choiceList.get(i)) {
                listOfMeal.add((MealEntity) product);
            }
        }

        for (FnBProductEntity product : choiceList.get(choiceList.size() - 1)) {
            listOfSet.add((SetMealEntity) product);
        }

        changeReservedMeal(reservation, listOfMeal, listOfReservedMeal, priceList, quantityList);
        changeReservedSet(reservation, listOfSet, listOfReservedSet, priceList, quantityList);

        //merge the new info of reservation
        entityManager.merge(reservation);
    }

    /*
     * Purpose: To change the table booked in this reservation
     * Steps:
     * 1) Introduce a boolean array to check whether this table type
     * is still chosen in the revised reservation
     * 2) Go through the full table list and check for each one if 
     * the quantity booked is > 0. If yes, go through the old reservation
     * and check whether it was booked
     * a) If yes, change the quantity of table booked
     * b) If no, create new reserved table and put into the list
     * 3) Go through the old list and check if which table type is not
     * reserved in the new reservation, then remove it.
     */
    private void changeReservedTable(FnBReservationEntity reservation, List<ReservedTableEntity> listOfReservedTable, List<TableTypeEntity> tableList, int[] selectedQuantity) {

        //to check whether this chosen table type is still chosen in the revised reservation
        boolean[] chosenTable = new boolean[listOfReservedTable.size()];
        int sizeOfTable = listOfReservedTable.size();

        for (int i = 0; i < tableList.size(); i++) {
            if (selectedQuantity[i] > 0) {
                int pos = 0;
                //find the position of this table type in the reserved table type list
                for (; pos < sizeOfTable; pos++) {
                    if (tableList.get(i).equals(listOfReservedTable.get(pos).getTableType())) {
                        break;
                    }
                }

                //if this one is already chosen
                if (pos != sizeOfTable) {
                    //set the corresponding value to true, indicates it's still chosen
                    chosenTable[pos] = true;
                    //and change the quantity to the new one
                    listOfReservedTable.get(pos).setQuantity(selectedQuantity[i]);
                    entityManager.merge(listOfReservedTable.get(pos));
                } else {
                    //or if it is a new table type chosen, create the new reserved table
                    ReservedTableEntity res = new ReservedTableEntity();
                    res.setQuantity(selectedQuantity[i]);
                    res.setTableType(tableList.get(i));
                    entityManager.persist(res);
                    tableList.get(i).getReservedTable().add(res);
                    res.setFandBReservation(reservation);
                }
            }
        }

        //go through the old reserved table type list and check which one is not chosen
        //in the revised reservation
        for (int i = sizeOfTable - 1; i >= 0; i--) {
            if (!chosenTable[i]) {
                //just remove it
                ReservedTableEntity res = listOfReservedTable.get(i);
                //remove the relationship with other entities
                res.getTableType().getReservedTable().remove(res);
                res.getFandBReservation().getReservedTable().remove(res);
                entityManager.merge(res);
                entityManager.remove(entityManager.find(ReservedTableEntity.class, res.getId()));
            }
        }
    }

    /*
     * Purpose: To change the meal booked in this reservation
     * Steps:
     * 1) Introduce a boolean array to check whether this meal
     * is still chosen in the revised reservation
     * 2) Go through the full meal list and check for each one if 
     * the quantity booked is > 0. If yes, go through the old reservation
     * and check whether it was booked
     * a) If yes, change the quantity of meal booked
     * b) If no, create new reserved meal and put into the list
     * 3) Go through the old list and check if which meal is not
     * reserved in the new reservation, then remove it.
     */
    private void changeReservedMeal(FnBReservationEntity reservation, List<MealEntity> listOfMeal, List<ReservedMealEntity> listOfReservedMeal, Map<Long, Double> priceList, Map<Long, Integer> quantityList) {
        int sizeOfMeal = listOfReservedMeal.size();
        boolean[] chosenMeal = new boolean[sizeOfMeal];

        for (int i = 0; i < listOfMeal.size(); i++) {
            int pos = 0;
            //find the position of this meal in the reserved meal list
            for (; pos < sizeOfMeal; pos++) {
                if (listOfMeal.get(i).equals(listOfReservedMeal.get(pos).getMeal())) {
                    break;
                }
            }

            //if this one is already chosen
            if (pos != sizeOfMeal) {
                //set the corresponding value to true, indicates it's still chosen
                chosenMeal[pos] = true;
                //and change the quantity to the new one
                listOfReservedMeal.get(pos).setQuantity(quantityList.get(listOfMeal.get(i).getId()));
            } else {
                //or if it is a new meal chosen, create the new reserved table
                MealEntity meal = listOfMeal.get(i);
                int quantity = quantityList.get(meal.getId());
                double onlinePrice = priceList.get(meal.getId());

                ReservedMealEntity resMeal = new ReservedMealEntity();
                resMeal.setPrice(onlinePrice);
                resMeal.setQuantity(quantity);
                resMeal.setDeposit(onlinePrice * meal.getDeposit() / 100);
                resMeal.setShowCharge(onlinePrice * meal.getShowCharge() / 100);
                resMeal.setNoShowCharge(onlinePrice * meal.getNoShowCharge() / 100);
                resMeal.setPointReceived(onlinePrice * meal.getPointReceived() / 100);
                entityManager.persist(resMeal);

                resMeal.setFandBReservation(reservation);
                listOfReservedMeal.add(resMeal);

                resMeal.setMeal(meal);
                meal.getReservedMeal().add(resMeal);
                entityManager.merge(meal);
                entityManager.merge(resMeal);
            }
        }

        //go through the old reserved meal list and check which one is not chosen
        //in the revised reservation
        for (int i = sizeOfMeal - 1; i >= 0; i--) {
            if (!chosenMeal[i]) {
                //just remove it
                ReservedMealEntity res = listOfReservedMeal.get(i);
                res.getMeal().getReservedMeal().remove(res);
                res.getFandBReservation().getReservedMeal().remove(res);
                entityManager.merge(res);
                entityManager.remove(entityManager.find(ReservedMealEntity.class, res.getId()));
            }
        }
    }

    /*
     * Purpose: To change the set meal booked in this reservation
     * Steps:
     * 1) Introduce a boolean array to check whether this set meal
     * is still chosen in the revised reservation
     * 2) Go through the full set meal list and check for each one if 
     * the quantity booked is > 0. If yes, go through the old reservation
     * and check whether it was booked
     * a) If yes, change the quantity of set meal booked
     * b) If no, create new reserved set meal and put into the list
     * 3) Go through the old list and check if which set meal is not
     * reserved in the new reservation, then remove it.
     */
    private void changeReservedSet(FnBReservationEntity reservation, List<SetMealEntity> listOfSet, List<ReservedSetMealEntity> listOfReservedSet, Map<Long, Double> priceList, Map<Long, Integer> quantityList) {
        int sizeOfSet = listOfReservedSet.size();
        boolean[] chosenSet = new boolean[listOfReservedSet.size()];

        for (int i = 0; i < listOfSet.size(); i++) {
            int pos = 0;
            //find the position of this meal in the reserved meal list
            for (; pos < sizeOfSet; pos++) {
                if (listOfSet.get(i).equals(listOfReservedSet.get(pos).getSetMeal())) {
                    break;
                }
            }

            //if this one is already chosen
            if (pos != sizeOfSet) {
                //set the corresponding value to true, indicates it's still chosen
                chosenSet[pos] = true;
                //and change the quantity to the new one
                listOfReservedSet.get(pos).setQuantity(quantityList.get(listOfSet.get(i).getId()));
                entityManager.merge(listOfReservedSet.get(pos));
            } else {
                //or if it is a new meal chosen, create the new reserved table
                SetMealEntity set = listOfSet.get(i);
                int quantity = quantityList.get(set.getId());
                double onlinePrice = priceList.get(set.getId());

                ReservedSetMealEntity resSet = new ReservedSetMealEntity();
                resSet.setPrice(onlinePrice);
                resSet.setQuantity(quantity);
                resSet.setDeposit(onlinePrice * set.getDeposit() / 100);
                resSet.setShowCharge(onlinePrice * set.getShowCharge() / 100);
                resSet.setNoShowCharge(onlinePrice * set.getNoShowCharge() / 100);
                resSet.setPointReceived(onlinePrice * set.getPointReceived() / 100);
                entityManager.persist(resSet);

                resSet.setFandBReservation(reservation);
                listOfReservedSet.add(resSet);

                resSet.setSetMeal(set);
                set.getReservedSetMeal().add(resSet);
                entityManager.merge(set);
            }
        }

        //go through the old reserved meal list and check which one is not chosen
        //in the revised reservation
        for (int i = sizeOfSet - 1; i >= 0; i--) {
            if (!chosenSet[i]) {
                //just remove it
                ReservedSetMealEntity res = listOfReservedSet.get(i);
                res.getSetMeal().getReservedSetMeal().remove(res);
                res.getFandBReservation().getReservedSetMeal().remove(res);
                entityManager.merge(res);
                entityManager.remove(entityManager.find(ReservedSetMealEntity.class, res.getId()));
            }
        }
    }

    public List<ReservationHelper> getReservationHelperListByTouristId(Long touristId) {
        List<FnBReservationEntity> tempList = getFnBReservationListByTouristId(Long.valueOf(touristId));
        List<ReservationHelper> reservationList = new ArrayList<ReservationHelper>();

        for (FnBReservationEntity res : tempList) {
            if (res.getStatus().equalsIgnoreCase("Show")) {
                reservationList.add(new ReservationHelper(res, (FnBBranchEntity) res.getBranch()));
            }
        }
        return reservationList;
    }

    // Hieu-start   
    public List<FnBReservationEntity> getFnBReservationByStakeholder(StakeholderEntity stakeholder) {
        List<FnBReservationEntity> result = new ArrayList<FnBReservationEntity>();
        List<BranchEntity> branchList = new ArrayList<BranchEntity>();
        branchList = stakeholder.getBranches();

        for (BranchEntity b : branchList) {
            List<ReservationEntity> tempReservationList = b.getReservation();
            for (ReservationEntity r : tempReservationList) {
                if (r instanceof FnBReservationEntity) {
                    FnBReservationEntity tempFnBReservation = (FnBReservationEntity) r;
                    if (tempFnBReservation.getStatus() != null && tempFnBReservation.getStatus().equalsIgnoreCase("Show")) {
                        result.add(tempFnBReservation);
                    }
                }
            }
        }
        return result;
    }
    //Hieu-end

    private String getDateFormat(Date date) {
        return date.getDate() + "/" + (date.getMonth() + 1) + "/" + (date.getYear() + 1900);
    }

    public String getTimeFormat(Time time) {
        return time.getHours() + ":" + time.getMinutes();
    }

    public double getChangeFeeByReservationId(Long transId) {
        FnBReservationEntity res = entityManager.find(FnBReservationEntity.class, transId);
        return ((FnBBranchEntity) res.getBranch()).getFnBIOTR().getChangeFee();
    }

    public void createCancelReservationPaymenItem(double amountToStakeholder, Date date, Long resId) {
        System.out.println("begin create cancel reservation payment item");
        ReservationPaymentItemEntity payment = new ReservationPaymentItemEntity();
        FnBReservationEntity reservation = entityManager.find(FnBReservationEntity.class, resId);
        StakeholderEntity stakeholder = reservation.getBranch().getStakeholder();
        payment.setAmountToStakeholder(amountToStakeholder);
        payment.setDateCreated(date);
        payment.setExplanation("Change fee applied for reservation id " + reservation.getId()
                + " cancelled on date " + getFormatReservedDate(date));
        payment.setReservation(reservation);
        payment.setStakeholder(stakeholder);
        reservation.getReservationPaymentItem().add(payment);
        stakeholder.getPaymentItem().add(payment);
        System.out.println("end method");
        entityManager.persist(payment);
        entityManager.merge(stakeholder);
        entityManager.merge(reservation);
    }

    public String getFormatReservedDate(Date date) {
        return date.getDate() + "/" + (date.getMonth() + 1)
                + "/" + (date.getYear() + 1900);
    }

    public void cancelReservation(long resId) throws IOException {
        FnBReservationEntity reservation = entityManager.find(FnBReservationEntity.class, resId);
        reservation.setConfirmation("Cancelled");
        List<TransactionsEntity> list = reservation.getTransactions();
        double cancelFee = CANCELFEE;
        int count = 0;
        double changeFee = getChangeFeeByReservationId(reservation.getId());
        int num = list.size();
        for (; count < num; count++) {
            TransactionsEntity trans = list.get(count);
            double amount = trans.getAmount();
            if (count != 0) {
                amount -= changeFee;
            }

            if (amount >= cancelFee) {
                amount -= cancelFee;
                cancelFee = 0;
            } else {
                cancelFee -= amount;
                amount = 0;
            }
            if (amount > 0) {
                paypalSessionBean.refundPayPal(trans, amount, "Refund for cancelling reservation "
                        + reservation.getId(), "Refund");
            }
        }

        double amountToStakeholder = (count - 1) * changeFee;
        if (amountToStakeholder != 0) {
            createCancelReservationPaymenItem(amountToStakeholder, new Date(), reservation.getId());
        }
    }

    public void updateBasicInfoOfReservation(FnBReservationEntity reservation) {
        entityManager.merge(reservation);
    }
}
